package com.gotobject.junit.internal;

import com.gotobject.junit.internal.runners.model.PerformanceKey;
import com.gotobject.junit.internal.runners.model.Measure;
import org.junit.internal.TextListener;
import org.junit.runner.Description;
import org.junit.runner.Result;
import org.junit.runner.notification.Failure;

import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

/**
 * @author Huascar A. Sanchez (huascar.sanchez@gmail.com)
 */
public class JUnitPerformanceTextListener extends TextListener {
    private final Map<PerformanceKey, Map<String, Collection<Description>>> results;
    private final PrintStream writer;

    private static final String SKIPPED = "Skipped";
    private static final String FAILED  = "Failed";
    private static final String PASSED  = "Passed";

    public JUnitPerformanceTextListener(PrintStream writer) {
        super(writer);
        this.writer = writer;
        results     = newHashMap();
        initResults();
    }

    private void initResults(){
        initTestLiteral();
        initRestultType();
    }

    private void initTestLiteral(){
        results.put(PerformanceKey.LOAD, new HashMap<String, Collection<Description>>());
        results.put(PerformanceKey.RESPONSE, new HashMap<String, Collection<Description>>());
        results.put(PerformanceKey.THROUGHPUT, new HashMap<String, Collection<Description>>());
        results.put(PerformanceKey.UNKNOWN, new HashMap<String, Collection<Description>>());
    }

    private void initRestultType(){
        results.get(PerformanceKey.LOAD).put(SKIPPED, new ArrayList<Description>());
        results.get(PerformanceKey.LOAD).put(FAILED, new ArrayList<Description>());
        results.get(PerformanceKey.LOAD).put(PASSED, new ArrayList<Description>());
        results.get(PerformanceKey.RESPONSE).put(SKIPPED, new ArrayList<Description>());
        results.get(PerformanceKey.RESPONSE).put(FAILED, new ArrayList<Description>());
        results.get(PerformanceKey.RESPONSE).put(PASSED, new ArrayList<Description>());
        results.get(PerformanceKey.THROUGHPUT).put(SKIPPED, new ArrayList<Description>());
        results.get(PerformanceKey.THROUGHPUT).put(FAILED, new ArrayList<Description>());
        results.get(PerformanceKey.THROUGHPUT).put(PASSED, new ArrayList<Description>());
        results.get(PerformanceKey.UNKNOWN).put(SKIPPED, new ArrayList<Description>());
        results.get(PerformanceKey.UNKNOWN).put(FAILED, new ArrayList<Description>());
        results.get(PerformanceKey.UNKNOWN).put(PASSED, new ArrayList<Description>());
    }

    private static <K, V> Map<K, V> newHashMap(){
		return new HashMap<K, V>();
	}

    private PrintStream output(){
        return writer;
    }

    protected void printBody() {
        output().println("ADDITIONAL PERFORMANCE INFO!!!");
        output().println("......................................................................................................................................................");
        for(PerformanceKey each : results.keySet()){
            final Collection<Description> passed  = results.get(each).get(PASSED);
			final Collection<Description> skipped = results.get(each).get(SKIPPED);
			final Collection<Description> failed  = results.get(each).get(FAILED);

            if(!passed.isEmpty()){
                output().println("OK (" + passed.size() + " " + each.toString().toLowerCase() + " tests )");
                int count = 1;
                for(Description eachDesc : passed){
                    output().print(" " + count++ + ". " + toTestString(eachDesc));
                    output().println();
                }
            }

            if(!skipped.isEmpty()){
                output().println(SKIPPED + " (" + skipped.size()+ " " + each.toString().toLowerCase() + " tests )");
                int count = 1;
                for(Description eachDesc : skipped){
                    output().print(" " + count++ + ". " + toTestString(eachDesc));
                    output().println();
                }
            }

            if(!failed.isEmpty()){
                output().println(FAILED + " (" + failed.size()+ " " + each.toString().toLowerCase() + " tests )");
                int count = 1;
                for(Description eachDesc : skipped){
                    output().print(" " + count++ + ". " + toTestString(eachDesc));
                    output().println();
                }
            }

        }
        output().println("......................................................................................................................................................");
    }

    @Override
    public void testStarted(Description description) {
        // ouch! well since we are not touching the internals of JUnit what we are trying to do is a challenge
        // so it is fair game. When we decide to touche thoses internals, then may think of a better
        // and cleaner solution.
        final Registry.ExecutionRecord record = Registry.get().findRecord(description);
        if(record == null){
            super.testStarted(description);
        } else {
            record.executed();
            results.get(record.getPerformanceKey()).get(PASSED).add(description);
        }
    }


    @Override
    public void testFailure(Failure failure) {
        super.testFailure(failure);
        final Description               desc    = failure.getDescription();
        final Registry.ExecutionRecord  record  = Registry.get().findRecord(desc);
        if(record != null){
            record.executed();
            results.get(record.getPerformanceKey()).get(PASSED).remove(desc);
            results.get(record.getPerformanceKey()).get(FAILED).add(desc);
        }
    }

    @Override
    public void testIgnored(Description description) {
        super.testIgnored(description);
        final Registry.ExecutionRecord record = Registry.get().findRecord(description);
        if(record != null){
            record.skipped();
            results.get(record.getPerformanceKey()).get(PASSED).remove(description);
            results.get(record.getPerformanceKey()).get(SKIPPED).add(description);
        } else {
            // covers the case where the explosions were never reached, and we need a empty record
            // to be created to indicate that they test was ignored.
            final Registry.ExecutionRecord er = new Registry.ExecutionRecord(PerformanceKey.from(description)).skipped();
            Registry.get().register(description, er);
            results.get(er.getPerformanceKey()).get(SKIPPED).add(description);
        }
    }



    @Override
    public void testRunFinished(Result result) {
        super.testRunFinished(result);
        printBody();
    }

    private static String toTestString(Description desc){
        final Measure           cResult     = Registry.get().findRecord(desc).getMeasure();
        final String            fullName    = desc.getDisplayName();
        final String            displayName = fullName.substring(0, fullName.indexOf("("));
        final String            location    = fullName.substring(fullName.indexOf("(")+1, fullName.indexOf(")"));

        return new StringBuilder(location).append("#")
                .append(displayName)
                .append(" finished within => ")
                .append("max=")
                .append(cResult.getMaxElapsedTime()).append(" ms")
                .append(", avg=")
                .append(cResult.getAvgElapsedTime()).append(" ms")
                .append(", min=")
                .append(cResult.getMinElapsedTime()).append(" ms")
                .append(".")
            .toString();
    }
}
